// Copyright 2000-2020 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.

package me.kagami.myplugin.extensions;

import com.intellij.icons.AllIcons;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
import com.intellij.ide.projectView.impl.ProjectViewListener;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Alarm;
import me.kagami.myplugin.topic.CustomizeEventListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.*;

import static me.kagami.myplugin.topic.CustomizeTopics.MY_TOPIC;

public class CustomizeProjectNode extends AbstractTreeNode<VirtualFile> {

    private static final Key<Set<VirtualFile>> CUSTOMIZE_PROJECT_DIRS = Key.create("customize.files.or.directories");

    public CustomizeProjectNode(final Project project) {
        super(project, ProjectUtil.guessProjectDir(project));
        String customizeViewPaneName = CustomizeSettingsState.getInstance().getCustomizeViewPaneName();
        init(project, customizeViewPaneName);


        CustomizeEventListener lis = (p, name) -> {
            getCustomizeFiles(p).clear();
            init(p, name);
            SwingUtilities.invokeLater(() -> ProjectView.getInstance(p).getProjectViewPaneById(CustomizeProjectViewPane.ID).updateFromRoot(true));
        };
        project.getMessageBus().connect().subscribe(MY_TOPIC, lis);
        subscribeToVFS(project, customizeViewPaneName);

        project.getMessageBus().connect().subscribe(ProjectViewListener.TOPIC, new ProjectViewListener() {
            public void paneShown(@NotNull AbstractProjectViewPane current, @Nullable AbstractProjectViewPane previous) {
                getCustomizeFiles(project).clear();
                init(project, customizeViewPaneName);
                SwingUtilities.invokeLater(() -> ProjectView.getInstance(project).getProjectViewPaneById(CustomizeProjectViewPane.ID).updateFromRoot(true));
            }
        });
    }

    private void init(Project project, String customizeViewPaneName) {
        if (customizeViewPaneName != null) {
            String[] split = customizeViewPaneName.split("#");
            for (String fileName : split) {
                scan(project, fileName);
            }
        }
    }

    public CustomizeProjectNode(Project project, VirtualFile file) {
        super(project, file);
    }

    // Creates a collection of image files asynchronously
    private void scan(Project project, String fileName) {
        final Set<VirtualFile> customizeFiles = getCustomizeFiles(project);
        final VirtualFile projectDir = ProjectUtil.guessProjectDir(project);

        try {
            final Collection<VirtualFile> files = ReadAction.compute(() -> FilenameIndex.getVirtualFilesByName(project, fileName, new GlobalSearchScope() {
                @Override
                public boolean isSearchInModuleContent(@NotNull Module module) {
                    return false;
                }
                @Override
                public boolean isSearchInLibraries() {
                    return false;
                }
                @Override
                public boolean contains(@NotNull VirtualFile virtualFile) {
                    return virtualFile.getName().equals(fileName);
                }
            }));
            for (VirtualFile file : files) {
                while (file != null && !file.equals(projectDir)) {
                    customizeFiles.add(file);
                    file = file.getParent();
                }
            }

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    @NotNull
    private Set<VirtualFile> getCustomizeFiles(Project project) {
        Set<VirtualFile> files = project.getUserData(CUSTOMIZE_PROJECT_DIRS);
        if (files == null) {
            files = new HashSet<>();
            project.putUserData(CUSTOMIZE_PROJECT_DIRS, files);
        }
        return files;
    }

    @Override
    protected VirtualFile getVirtualFile() {
        return getValue();
    }

    @NotNull
    @Override
    public Collection<? extends AbstractTreeNode<?>> getChildren() {
        final List<VirtualFile> files = new ArrayList<>(0);
        for (VirtualFile file : getValue().getChildren()) {
            if (getCustomizeFiles(myProject).contains(file)) {
                files.add(file);
            }
        }
        if (files.isEmpty()) {
            return Collections.emptyList();
        }
        final List<AbstractTreeNode<?>> nodes = new ArrayList<>(files.size());
        final boolean alwaysOnTop = ProjectView.getInstance(myProject).isFoldersAlwaysOnTop("");
        files.sort((o1, o2) -> {
            if (alwaysOnTop) {
                final boolean d1 = o1.isDirectory();
                final boolean d2 = o2.isDirectory();
                if (d1 && !d2) {
                    return -1;
                }
                if (!d1 && d2) {
                    return 1;
                }
            }

            return StringUtil.naturalCompare(o1.getName(), o2.getName());
        });
        for (VirtualFile file : files) {
            nodes.add(new CustomizeProjectNode(myProject, file));
        }
        return nodes;
    }

    @Override
    protected void update(PresentationData data) {
        data.setIcon(getValue().isDirectory() ? AllIcons.Nodes.Folder : getValue().getFileType().getIcon());
        data.setPresentableText(getValue().getName());
    }

    @Override
    public boolean canNavigate() {
        return !getValue().isDirectory();
    }

    @Override
    public boolean canNavigateToSource() {
        return canNavigate();
    }

    @Override
    public void navigate(boolean requestFocus) {
        FileEditorManager.getInstance(myProject).openFile(getValue(), false);
    }

    private void subscribeToVFS(final Project project, String customizeViewPaneName) {
        final Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, project);

        LocalFileSystem.getInstance().addVirtualFileListener(new VirtualFileListener() {
            {
                final VirtualFileListener me = this;
                Disposer.register(project, () -> LocalFileSystem.getInstance().removeVirtualFileListener(me));
            }

            @Override
            public void fileCreated(@NotNull VirtualFileEvent event) {
                handle(event);
            }

            @Override
            public void fileDeleted(@NotNull VirtualFileEvent event) {
                handle(event);
            }

            void handle(VirtualFileEvent event) {
                if (customizeViewPaneName != null) {
                    alarm.cancelAllRequests();
                    alarm.addRequest(() -> {
                        getCustomizeFiles(project).clear();
                        String[] split = customizeViewPaneName.split("#");
                        for (String fileName : split) {
                            final String filename = event.getFileName().toLowerCase();
                            if (filename.equals(fileName)) {
                                scan(project, fileName);
                            }
                        }
                        SwingUtilities.invokeLater(() -> ProjectView.getInstance(myProject).getProjectViewPaneById(CustomizeProjectViewPane.ID).updateFromRoot(true));
                    }, 1000);
                }

            }
        });
    }

}
